#pragma once
#include "../Net.hpp"
#include "../Unit.hpp"
#include "../Dispatch.hpp"
#include <unordered_set>
#include <unordered_map>
/*
模块功能说明:给服务中心主机使用，负责管理发布主题的连接者和订阅主题的连接者。
管理哪个主机(链接)发布了什么主题，哪个主机（链接）订阅了什么主题，这个主题哪些链接发布了
等等，链接断开时删除对应的记录，比如主机A发布了music，qq主题，主机A与服务中心断开了连接
那么就删除主机A的链接和对应发布的主题的关系，比如主机B下线了，删除主机B和它订阅的主题的记录

再定义一个回调函数来处理订阅请求的回调函数注册到Dispatch模块中

服务主机只负责管理订阅者的信息，对于创建者是不管理的，因为任何一台主机都能创建主题
也能订阅主题也能推送主题内容，当然推送主题和订阅主题可以在同一台主机的同一个进程，只需要和
服务中心发起两次链接（即创建了两个socket fd去发送或者接收服务中心的信息）就行了，
当负责推送主题内容的链接断开服务中心不做处理，负责订阅的连接断开服务中心就要删除其对应订阅记录

接收的主题请求中的正文中除了主题内容发布请求，其它的请求应该是这样的json字符串
:{"topic": "xxx","toper_type" : 0}即只有主题字段和主题操作字段这两个字段
而主题内容发布请求：{"topic": "xxx","toper_type" : 0,"topic_msg":"xxxxxxxx"}
多了个主题内容字段，对应该请求服务中心则是直接把该请求推送给订阅的客户端，
*/


namespace Topic
{

    class TopicManager
    {
    public:
        using TopicMPtr = std::shared_ptr<TopicManager>;
        // 处理Topic请求(比如主题创建请求)的回调函数将来注册到Dispather模块中去
        void DealTopicRequest(const Abstract::BaseConnection::ConPtr &CPtr, const Abstract::BaseMessage::MesaPtr &Mptr)
        {
            // 1 主题创建 主题删除 主题订阅 缺陷主题订阅 主题消息的发布
            Message::TopicRequest::TopicPtr TopReqMessage = std::dynamic_pointer_cast<Message::TopicRequest>(Mptr);
            TopicOperType Type = TopReqMessage->GetTopOper();
            bool ret = true;
            switch (Type)
            {
            // 1主题创建
            case TopicOperType::Topic_Create:
                CreateTopic(CPtr, TopReqMessage);
                break;
            // 2 主题订阅
            case TopicOperType::Topic_SubScribe:
                ret = subscribeTopic(CPtr, TopReqMessage);
                break;
            // 3 主题删除
            case TopicOperType::Topic_Delete:
                RemoveTopic(CPtr, TopReqMessage);
                break;
            // 4 取消订阅
            case TopicOperType::Topic_Cancle_Sub:
                ret = CanselSubTopic(CPtr, TopReqMessage);
                break;
            // 5 推送主题内容，先给订阅客户端推送再给请求方回应
            case TopicOperType::Topic_SendMesg:
                ret = PushTopicMsg(CPtr, TopReqMessage);
                break;
            default:
                ret = false;
                break;
            }
            // 对主题请求操作进行响应，正文中只需要设置状态码即可，请求方只需要知道请求成功没有
            // 主题请求中的操作失败进行回应
            if (ret == false)
            {
                MakeErrTopicRes(CPtr,TopReqMessage-> GetUuid());
            }
            else
            {
                MakeOkTopicRes(CPtr,TopReqMessage-> GetUuid());
            }
        }
        //链接断开的回调函数将来注册到服务端对象中作为回调函数使用
        void ShutDownCBack(const Abstract::BaseConnection::ConPtr &CPtr)
        {
            //1 如果是主题内容发布者链接断开则不用处理
            if(_Subscribers.count(CPtr)<=0)
                return;
            
            //2 订阅者链接断开，服务中心删除订阅者的订阅记录
            //存储订阅者订阅的主题
            std::vector<std::string> topics;
            for(auto& topic:_Subscribers[CPtr]->_TopicNames)
            {
                topics.emplace_back(topic);
            }
            //删除订阅者和主题的对于记录
            _Subscribers.erase(CPtr);
            //再删除主题和订阅者的关系
            for(auto& topic:topics)
            {
                if(_topics.count(topic))
                {
                    _topics[topic]->RemoveSubscribers(CPtr);
                }
            }
            

        }
    private:
        void MakeErrTopicRes(const Abstract::BaseConnection::ConPtr &CPtr,const std::string& uuid)
        {
            Message::TopicResponceJson::TPPtr TopicRePtr = Message::BuildMessage::build<Message::TopicResponceJson>();
            TopicRePtr->SetMtype(Rs_RqType::TOPIC_Res);
            TopicRePtr->SetUuid(uuid);
            TopicRePtr->Setcode(ResCode::ResCode_NOT_FOUND_TOPIC);//对于主题请求的回应只需要设置状态码即可
            CPtr->Send(TopicRePtr);
        }
        void MakeOkTopicRes(const Abstract::BaseConnection::ConPtr &CPtr,const std::string& uuid)
        {
            Message::TopicResponceJson::TPPtr TopicRePtr = Message::BuildMessage::build<Message::TopicResponceJson>();
            TopicRePtr->SetMtype(Rs_RqType::TOPIC_Res);
            TopicRePtr->SetUuid(uuid);
            TopicRePtr->Setcode(ResCode::ResCode_Ok);//对于主题请求的回应只需要设置状态码即可
            CPtr->Send(TopicRePtr);
        }

    private:
        void CreateTopic(const Abstract::BaseConnection::ConPtr &CPtr, const Message::TopicRequest::TopicPtr &Toreqeust)
        {
            std::unique_lock<std::mutex> Lock(_mutex);
            auto topicname = Toreqeust->GetTopic();
            Topic::TopicPtr Tptr = std::make_shared<Topic>(topicname);
            // 创建当前主题和当前订阅者的关系，
            _topics.insert({topicname, Tptr});
        }
        // 主题删除请求对应的操作
        void RemoveTopic(const Abstract::BaseConnection::ConPtr &CPtr, const Message::TopicRequest::TopicPtr &Toreqeust)
        {
            std::unique_lock<std::mutex> Lock(_mutex);
            auto topicname = Toreqeust->GetTopic();
            // 1 先删除对应的订阅者的消息，删除之前先保存对应的订阅者，因为该订阅者还要删除自己订阅该主题的记录
            if (_topics.count(topicname) <= 0)
            {
                ELOG("该主题不存在");
                return;
            }
            if (_topics[topicname]->_subscribes.empty())
            {
                ELOG("该主题不存在订阅者");
                return;
            }
            // 3 删除订阅者的对应的订阅记录
            for (auto &Cptr : _topics[topicname]->_subscribes)
            {
                if (_Subscribers.count(Cptr)) // 如果该订阅者存在
                {
                    _Subscribers[Cptr]->RemoveTopicName(topicname); // 删除订阅者的对应的订阅记录
                }
                else
                {
                    ELOG("不存在该订阅者");
                }
            }
            // 4 删除该主题和 它的订阅者们
            _topics.erase(topicname);
        }
        // 主题订阅请求对应的操作
        bool subscribeTopic(const Abstract::BaseConnection::ConPtr &CPtr, const Message::TopicRequest::TopicPtr &Toreqeust)
        {
            // 1 找到对应注册的主题，没有主题说明没有创建该主题
            std::unique_lock<std::mutex> Lock(_mutex);
            auto topicname = Toreqeust->GetTopic();
            if (_topics.count(topicname) <= 0)
            {
                ELOG("该主题不存在");
                return false;
            }
            // 2 找到对应的订阅者，没有找到说明是第一次订阅主题
            // 找到说明以前订阅过主题
            SubscribeTopic::SubscribeTopicPtr SubPtr;
            if (_Subscribers.count(CPtr)) // 如果该订阅者存在
            {
                // 添加订阅者和主题的关系
                _Subscribers[CPtr]->AddTopicName(topicname);
            }
            else
            {
                ELOG("不存在该订阅者");
                // 第一次订阅创建订阅者和订阅主题的关系，创建订阅关系
                SubscribeTopic::SubscribeTopicPtr Ptr = std::make_shared<SubscribeTopic>(CPtr);
                Ptr->AddTopicName(topicname);
                // 添加订阅者和订阅主题的关系
                _Subscribers.insert({CPtr, Ptr});
            }
            // 添加主题和订阅者的关系
            _topics[topicname]->AddSubscribers(CPtr);
            return true;
        }
        // 主题取消订阅请求对应的操作
        bool CanselSubTopic(const Abstract::BaseConnection::ConPtr &CPtr, const Message::TopicRequest::TopicPtr &Toreqeust)
        {
            // 1 找到对应注册的主题，没有主题说明没有创建该主题
            std::unique_lock<std::mutex> Lock(_mutex);
            auto topicname = Toreqeust->GetTopic();
            if (_topics.count(topicname) <= 0)
            {
                ELOG("该主题不存在");
                return false;
            }
            // 2 找到对应的订阅者，没有找到说明该链接没有订阅过该主题不做处理
            // 找到说明以前订阅过主题,再判断有没有订阅该主题
            SubscribeTopic::SubscribeTopicPtr SubPtr;
            if (_Subscribers.count(CPtr)) // 如果该订阅者存在
            {
                // 如果该订阅者订阅了该主题
                if (_Subscribers[CPtr]->_TopicNames.count(topicname))
                {
                    _Subscribers[CPtr]->RemoveTopicName(topicname);
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                ELOG("不存在该订阅者");
                return false;
            }
            // 删除主题和订阅者的关系
            _topics[topicname]->RemoveSubscribers(CPtr);
            ;
            return true;
        }

        // 主题内容发布请求对应的操作
        bool PushTopicMsg(const Abstract::BaseConnection::ConPtr &CPtr, const Message::TopicRequest::TopicPtr &Toreqeust)
        {
            // 1 找到对应注册的主题，没有主题说明没有创建该主题则不发布内容
            std::unique_lock<std::mutex> Lock(_mutex);
            auto topicname = Toreqeust->GetTopic();
            if (_topics.count(topicname) <= 0)
            {
                ELOG("该主题不存在");
                return false;
            }
            // 2 找到对应的订阅者，没有找到说明没有订阅者订阅该主题
            if (_topics[topicname]->_subscribes.empty())
            {
                return false;
            }
            // 3 推送主题的内容个给相关的订阅者

            _topics[topicname]->PushTopicMsg(Toreqeust);

            return true;
        }

    private:
        // 1 定义一个结构体类型去描述主题有哪些订阅者，将来该主题下线时或者主题对应的内容发布时
        // 方便通知每个订阅者
        struct Topic
        {
            using TopicPtr = std::shared_ptr<Topic>;
            std::mutex _mutex;
            std::string _TopicName;                                           // 发布的主题名称
            std::unordered_set<Abstract::BaseConnection::ConPtr> _subscribes; // 当前主题的订阅者
            Topic(const std::string &name) : _TopicName(name)
            {
            }

            // 为该主题添加一个订阅者
            void AddSubscribers(const Abstract::BaseConnection::ConPtr &Con)
            {
                std::unique_lock<std::mutex> Lock(_mutex);
                if (_subscribes.count(Con) <= 0)
                {
                    _subscribes.emplace(Con);
                }
            }
            // 订阅者取消主题订阅时调用
            void RemoveSubscribers(const Abstract::BaseConnection::ConPtr &Con)
            {
                std::unique_lock<std::mutex> Lock(_mutex);
                if (_subscribes.count(Con))
                {
                    _subscribes.erase(Con);
                }
            }
            // 收到主题内容发布请求时调用推送内容给订阅者
            void PushTopicMsg(const Abstract::BaseMessage::MesaPtr &Mptr)
            {
                std::unique_lock<std::mutex> Lock(_mutex);
                for (auto &subscriber : _subscribes)
                {
                    subscriber->Send(Mptr);
                }
            }
        };
        // 2 定义一个结构体描述订阅者和主题的关系
        // 这个订阅者订阅了哪些消息，如果将来订阅者下线了方便删除主题中对应的连接者
        // 这样以后如果主题有内容发布就不用通知下线的订阅者了
        struct SubscribeTopic
        {
            using SubscribeTopicPtr = std::shared_ptr<SubscribeTopic>;
            std::mutex _mutex;
            Abstract::BaseConnection::ConPtr _Conptr;    // 订阅者
            std::unordered_set<std::string> _TopicNames; // 订阅者订阅的主题
            SubscribeTopic(const Abstract::BaseConnection::ConPtr &Con)
                : _Conptr(Con)
            {
            }
            // 订阅者订阅主题时调用
            void AddTopicName(const std::string &name)
            {
                std::unique_lock<std::mutex> Lock(_mutex);
                if (_TopicNames.count(name) <= 0)
                {
                    _TopicNames.emplace(name);
                }
            }
            // 主题被删除或者订阅者取消订阅时调用
            void RemoveTopicName(const std::string &name)
            {
                std::unique_lock<std::mutex> Lock(_mutex);
                if (_TopicNames.count(name))
                {
                    _TopicNames.erase(name);
                }
            }
        };

    private:
        std::mutex _mutex;
        std::unordered_map<std::string, Topic::TopicPtr> _topics; // 主题和它的订阅者们
        // 订阅者和它订阅的主题们
        std::unordered_map<Abstract::BaseConnection::ConPtr, SubscribeTopic::SubscribeTopicPtr> _Subscribers;
    };

} // namespace Topic
